// eslint-disable-next-line import/no-nodejs-modules
import { Duplex } from 'stream';
// @ts-expect-error - No types declarations
import pump from 'pump';

import { JsonRpcEngine, JsonRpcMiddleware } from '@metamask/json-rpc-engine';
// @ts-expect-error - No types declarations
import createFilterMiddleware from '@metamask/eth-json-rpc-filters';
// @ts-expect-error - No types declarations
import createSubscriptionManager from '@metamask/eth-json-rpc-filters/subscriptionManager';
import { JsonRpcParams, Json } from '@metamask/utils';
import {
  createSelectedNetworkMiddleware,
  SelectedNetworkControllerMessenger,
} from '@metamask/selected-network-controller';
import { createPreinstalledSnapsMiddleware } from '@metamask/snaps-rpc-methods';
import { SubjectType } from '@metamask/permission-controller';
import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware';
import { createEngineStream } from '@metamask/json-rpc-middleware-stream';
import { SnapId } from '@metamask/snaps-sdk';
import { InternalAccount } from '@metamask/keyring-internal-api';

import Engine from '../Engine';
import { setupMultiplex } from '../../util/streams';
import Logger from '../../util/Logger';
import { createOriginMiddleware } from '../../util/middlewares';
import { RPCMethodsMiddleParameters } from '../RPCMethods/RPCMethodMiddleware';
import snapMethodMiddlewareBuilder from './SnapsMethodMiddleware';
import { isSnapPreinstalled } from '../SnapKeyring/utils/snaps';

/**
 * Type definition for the GetRPCMethodMiddleware function.
 */
type GetRPCMethodMiddleware = ({
  hostname,
  getProviderState,
}: {
  hostname: RPCMethodsMiddleParameters['hostname'];
  getProviderState: RPCMethodsMiddleParameters['getProviderState'];
}) => JsonRpcMiddleware<JsonRpcParams, Json>;

/**
 * A bridge for connecting the client Ethereum provider to a Snap's execution environment.
 *
 * @param params - The parameters for the SnapBridge.
 * @param params.snapId - The ID of the Snap.
 * @param params.connectionStream - The stream to connect to the Snap.
 * @param params.getRPCMethodMiddleware - A function to get the RPC method middleware.
 */
export default class SnapBridge {
  #snapId: SnapId;
  #stream: Duplex;
  #getRPCMethodMiddleware: GetRPCMethodMiddleware;

  constructor({
    snapId,
    connectionStream,
    getRPCMethodMiddleware,
  }: {
    snapId: SnapId;
    connectionStream: Duplex;
    getRPCMethodMiddleware: GetRPCMethodMiddleware;
  }) {
    Logger.log('[SNAP BRIDGE] Initializing SnapBridge for Snap:', snapId);

    this.#snapId = snapId;
    this.#stream = connectionStream;
    this.#getRPCMethodMiddleware = getRPCMethodMiddleware;
  }

  /**
   * Gets the provider state.
   * @returns An object containing the provider state.
   */
  #getProviderState() {
    return {
      isUnlocked: Engine.context.KeyringController.isUnlocked(),
    };
  }

  /**
   * Sets up the provider engine for the Snap.
   * @returns The configured JSON-RPC engine.
   */
  #setupProviderEngine() {
    Logger.log('[SNAP BRIDGE] Setting up provider engine');

    const { context, controllerMessenger } = Engine;
    const { SelectedNetworkController, PermissionController } = context;
    const engine = new JsonRpcEngine();

    const proxy = SelectedNetworkController.getProviderAndBlockTracker(
      this.#snapId,
    );

    const filterMiddleware = createFilterMiddleware(proxy);

    const subscriptionManager = createSubscriptionManager(proxy);
    subscriptionManager.events.on('notification', (message: Json) =>
      engine.emit('notification', message),
    );

    engine.push(
      createOriginMiddleware({ origin: this.#snapId }) as JsonRpcMiddleware<
        JsonRpcParams,
        Json
      >,
    );

    engine.push(
      createSelectedNetworkMiddleware(
        controllerMessenger as unknown as SelectedNetworkControllerMessenger,
      ),
    );

    // Filter and subscription polyfills
    engine.push(filterMiddleware);
    engine.push(subscriptionManager.middleware);

    if (isSnapPreinstalled(this.#snapId)) {
      engine.push(
        createPreinstalledSnapsMiddleware({
          getPermissions: PermissionController.getPermissions.bind(
            PermissionController,
            this.#snapId,
          ),
          getAllEvmAccounts: () =>
            controllerMessenger
              .call('AccountsController:listAccounts')
              .map((account: InternalAccount) => account.address),
          grantPermissions: (approvedPermissions) =>
            controllerMessenger.call('PermissionController:grantPermissions', {
              approvedPermissions,
              subject: { origin: this.#snapId },
            }),
        }),
      );
    }

    engine.push(
      PermissionController.createPermissionMiddleware({
        origin: this.#snapId,
      }),
    );

    engine.push(
      snapMethodMiddlewareBuilder(
        context,
        controllerMessenger,
        this.#snapId,
        SubjectType.Snap,
      ),
    );

    // User-Facing RPC methods
    engine.push(
      this.#getRPCMethodMiddleware({
        hostname: this.#snapId,
        getProviderState: this.#getProviderState.bind(this),
      }),
    );

    // Forward to metamask primary provider
    engine.push(providerAsMiddleware(proxy.provider));

    return engine;
  }

  /**
   * Sets up the provider connection for the Snap.
   */
  setupProviderConnection() {
    Logger.log('[SNAP BRIDGE] Setting up provider connection');

    const mux = setupMultiplex(this.#stream);
    const stream = mux.createStream('metamask-provider');

    const engine = this.#setupProviderEngine();

    const providerStream = createEngineStream({ engine });

    pump(stream, providerStream, stream, (error: Error | null) => {
      engine.destroy();

      if (error) {
        Logger.log('[SNAP BRIDGE] Error with provider stream:', error);
      }
    });
  }
}
